package com.lyf.export.service;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.afterturn.easypoi.handler.inter.IExcelExportServer;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ZipUtil;
import com.google.common.collect.Lists;
import com.lyf.common.api.ResultCode;
import com.lyf.common.exception.ApiException;
import com.lyf.export.service.model.AsyncExportTask;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.statement.update.Update;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;

/**
 * 异步导出任务
 *
 * @author yangdong
 * @since 2021/3/29 15:08
 */
@Component
@Slf4j
public class AsyncExportService {

    @Autowired
    private AsyncTaskExecutor asyncTaskExecutor;
    //@Autowired
    //private CosConfig cosConfig;
    //@Autowired
    //private CosService cosService;
    //@Autowired
    //private MongoTemplate mongoTemplate;
    @Value("${defaultBatchExportSize:100000}")
    private Integer defaultBatchExportSize;
    final static String fileNamePrefix = "https://static-1303934766.cos.ap-beijing.myqcloud.com/";
    private static final String FILE_EXT = ".xlsx";
    private static final String ZIP_EXT = ".zip";

    /**
     * 任务监听
     *
     * @param <T>
     */
    public abstract static class AbstractQueryTask<T, R> {

        /**
         * 执行方法
         *
         * @param t
         */
        public abstract List<R> invoke(T t);
    }
    /**
     * 动态列
     * @param <T>
     * @param <R>
     */
    public abstract static class AbstractDynamicQueryTask<T, R> {


        public abstract List<R> invoke(T t, List<ExcelExportEntity> entityList);
    }
    /**
     * 提交---异步任务
     *
     * @param queryTask
     * @param rClass
     * @param t
     * @param fileName
     * @param cosKey
     * @param taskId
     * @param <T>
     * @param <R>
     */
    public <T, R> void asyncInvoke(AbstractQueryTask<T, R> queryTask, Class<R> rClass, T t, String fileName, String cosKey, String taskId) {
        asyncTaskExecutor.submit(() -> {
            export(queryTask, rClass, t, fileName, cosKey, taskId);
        });
    }
    public <T, R> void asyncInvoke(AbstractDynamicQueryTask<T, R> queryTask,  T t, String fileName, String cosKey, String taskId) {
        asyncTaskExecutor.submit(() -> {
            export(queryTask, t, fileName, cosKey, taskId);
        });
    }
    /**
     * 大数据量分页导出
     * @param rClass 导出的类
     * @param queryParams 查询参数
     * @param server  分页查询接口
     * @param fileName
     * @param cosKey
     * @param taskId
     */
    public <T,R> void asyncBigDataInvoke(Class<T> rClass,R queryParams, IExcelExportServer server,String fileName, String cosKey, String taskId) {
        asyncTaskExecutor.submit(() -> {
            export(rClass,queryParams, server, fileName, cosKey, taskId);
        });
    }
    private <T,R>  void export(Class<T> rClass,R queryParams,IExcelExportServer server, String fileName, String cosKey, String taskId) {

        ExportParams params = new ExportParams(null, fileName, ExcelType.HSSF);
        Workbook workbook = null;
        try {
            workbook = ExcelExportUtil.exportBigExcel(params, rClass, server,queryParams);
        } catch (Exception e) {
            log.error("数据查询异常:{}", e);
            updateFail(taskId, "数据查询异常");
            throw new ApiException(ResultCode.FAILED, "数据查询异常");
        }
        String filePath = System.getProperty("java.io.tmpdir") + File.separator + (System.currentTimeMillis()) + ".xlsx";
        File file = new File(filePath);
        try {
            workbook.write(Files.newOutputStream(file.toPath()));
        } catch (IOException e) {
            log.error("数据写入异常:{}", e);
            updateFail(taskId, "数据写入异常");
            throw new ApiException(ResultCode.FAILED, "数据写入异常");
        }
        //上传
        upLoad(cosKey, fileName, file, taskId);

    }
    private <T, R> void export(AbstractDynamicQueryTask<T, R> queryTask, T t, String fileName, String cosKey, String taskId) {

        //查询数据
        List<R> queryRes = null;
        List<ExcelExportEntity> entityList = new ArrayList<>();
        try {
            queryRes = queryTask.invoke(t,entityList);
        } catch (Exception e) {
            log.error("数据查询异常：{}", e);
            updateFail(taskId, "查询数据异常");
        }

        File file = null;
        try {
            //将数据写入excel
            ExportParams params = new ExportParams(null, fileName, ExcelType.HSSF);
            Workbook workbook = ExcelExportUtil.exportExcel(params, entityList, queryRes);

            String filePath = System.getProperty("java.io.tmpdir") + File.separator + (System.currentTimeMillis()) + ".xls";
            file = new File(filePath);
            workbook.write(new FileOutputStream(file));
            //将数据上传到cos中，成功后将获得cos url
            //upLoadToCos(cosKey, fileName, file);
            //更新导出表信息（状态）
            String url = fileNamePrefix + cosKey + "/" + fileName;
            log.info("成功上传文档到cos,url:'{}'", url);
            updateSuccess(taskId, url);
        } catch (Exception e) {
            log.info("文件导出异常:{}", e);
            //更新导出表信息（异常状态）
            updateFail(taskId, e.getMessage());
        }
    }
    private <T, R> void export(AbstractQueryTask<T, R> queryTask, Class<R> rClass, T t, String fileName, String cosKey, String taskId) {

        //查询数据
        List<R> queryRes = null;
        try {
            queryRes = queryTask.invoke(t);
            log.info("导出数据条数:{}", queryRes.size());
        } catch (Exception e) {
            log.error("数据查询异常：{}", e);
            updateFail(taskId, "数据查询异常");
        }
        if (queryRes.size() > defaultBatchExportSize) {
            File zipFile = getZipFile(fileName, rClass, queryRes, taskId);
            //上传
            upLoadZip(cosKey, fileName, zipFile, taskId);
        } else {
            //将数据写入excel
            File file = getExcelFile(fileName, rClass, queryRes, taskId);
            //上传
            upLoad(cosKey, fileName, file, taskId);
        }

    }


    private File getZipFile(String fileName, Class rClass, List queryRes, String taskId) {
        File zipTemFile = getZipTemFilePath();
        if (!zipTemFile.isDirectory()) {
            zipTemFile.mkdirs();
        }
        fileName = fileName.substring(0, fileName.lastIndexOf("."));
        try {
            List<List> partition = Lists.partition(queryRes, defaultBatchExportSize);
            int i = 1;
            while (partition.size() != 0) {
                List list = partition.get(0);
                File file = new File(zipTemFile.getPath() + File.separator + fileName + "-" + i + FILE_EXT);
                ExportParams params = new ExportParams(null, fileName, ExcelType.HSSF);
                //此方法会将list的数据清空
                Workbook workbook = ExcelExportUtil.exportExcel(params, rClass, list);
                workbook.write(new FileOutputStream(file));
                i++;
            }

        } catch (Exception e) {
            log.error("数据写入异常：{}", e);
            updateFail(taskId, "数据写入异常");
            throw new ApiException(ResultCode.FAILED, "数据写入异常");
        }
        File zip = ZipUtil.zip(zipTemFile);
        FileUtil.del(zipTemFile);
        return zip;
    }

    /**
     * 得到临时的压缩文件
     *
     * @return
     */
    private File getZipTemFilePath() {
        String filePath = System.getProperty("java.io.tmpdir") + File.separator + (System.currentTimeMillis());
        return new File(filePath);
    }

    private File getExcelFile(String fileName, Class rClass, List queryRes, String taskId) {
        String filePath = System.getProperty("java.io.tmpdir") + File.separator + (System.currentTimeMillis()) + ".xls";
        File file = new File(filePath);
        try {
            ExportParams params = new ExportParams(null, fileName, ExcelType.HSSF);
            Workbook workbook = ExcelExportUtil.exportExcel(params, rClass, queryRes);
            workbook.write(new FileOutputStream(file));
        } catch (IOException e) {
            log.error("数据写入异常:{}", e);
            updateFail(taskId, "数据写入异常");
            throw new ApiException(ResultCode.FAILED, "数据写入异常");
        }
        return file;
    }

    private void upLoad(String cosKey, String fileName, File file, String taskId) {
        //将数据上传到cos中，成功后将获得cos url
        try {
            //upLoadToCos(cosKey, fileName, file);
            //更新导出表信息（状态）
            String url = fileNamePrefix + cosKey + "/" + fileName;
            log.info("成功上传文档到cos,url:'{}'", url);
            updateSuccess(taskId, url);
            FileUtil.del(file);
        } catch (Exception e) {
            log.error("文件上传cos异常:{}", e);
            //更新导出表信息（异常状态）
            updateFail(taskId, "文件上传cos异常");
            throw new ApiException(ResultCode.FAILED, "文件上传cos异常");
        }
    }

    private void upLoadZip(String cosKey, String fileName, File file, String taskId) {
        //将数据上传到cos中，成功后将获得cos url
        try {
            fileName = fileName.substring(0, fileName.lastIndexOf(".")) + ZIP_EXT;
            //upLoadToCos(cosKey, fileName, file);
            //更新导出表信息（状态）
            String url = fileNamePrefix + cosKey + "/" + fileName;
            log.info("成功上传文档到cos,url:'{}'", url);
            FileUtil.del(file);
            updateSuccess(taskId, url, fileName);
        } catch (Exception e) {
            log.error("文件上传cos异常:{}", e);
            //更新导出表信息（异常状态）
            updateFail(taskId, "文件上传cos异常");
            throw new ApiException(ResultCode.FAILED, "文件上传cos异常");
        }
    }

    public <R> void export(List<R> exportList, Class<R> rClass, String fileName, String cosKey, String taskId) {

        try {//将数据写入excel
            ExportParams params = new ExportParams(null, fileName, ExcelType.HSSF);
            Workbook workbook = ExcelExportUtil.exportExcel(params, rClass, exportList);
            String filePath = System.getProperty("java.io.tmpdir") + File.separator + (System.currentTimeMillis()) + ".xls";
            File file = new File(filePath);
            workbook.write(new FileOutputStream(file));
            //将数据上传到cos中，成功后将获得cos url
            //upLoadToCos(cosKey, fileName, file);
            //更新导出表信息（状态）
            String url = fileNamePrefix + cosKey + "/" + fileName;
            updateSuccess(taskId, url);
        } catch (Exception e) {
            log.error("文件导出异常，ex:{}", e);
            //更新导出表信息（异常状态）
            updateFail(taskId, e.getMessage());
        }
    }

    /**
     * 新增异步导出任务
     *
     * @param fileName
     * @param typeCode
     */
    public AsyncExportTask exportSave(String fileName, int typeCode) {
        AsyncExportTask task = new AsyncExportTask(fileName, typeCode);
        //task.setCreateTime(System.currentTimeMillis());
        //task.setType(Constant.CONST_1);
        task.setTaskTime(System.currentTimeMillis());
        //mongoTemplate.save(task);
        return task;
    }

    public AsyncExportTask importSave(String fileName, int typeCode) {
        AsyncExportTask task = new AsyncExportTask(fileName, typeCode);
        //task.setCreateTime(System.currentTimeMillis());
        //task.setType(Constant.CONST_2);
        task.setTaskTime(System.currentTimeMillis());
        //mongoTemplate.save(task);
        return task;
    }


    /**
     * 更新异步任务
     *
     * @param id
     * @param state
     * @param reason
     */
    public void update(String id, int state, String reason, String url, String fileName) {
        //Query query = new Query();
        //query.addCriteria(Criteria.where("_id").is(id));
        //
        //Update update = new Update();
        //if (state == 1) {
        //    update.set("url", url);
        //}
        //if (state == 2) {
        //    update.set("reason", reason);
        //}
        //if (StringUtils.isNotBlank(fileName)) {
        //    update.set("fileName", fileName);
        //}
        //update.set("state", state);
        //update.set("createTime", System.currentTimeMillis() / 1000 * 1000);
        //mongoTemplate.updateFirst(query, update, AsyncExportTask.class);
    }

    private void updateSuccess(String id, String url) {
        update(id, 1, null, url, null);
    }

    private void updateSuccess(String id, String url, String fileName) {
        update(id, 1, null, url, fileName);
    }

    public void updateFail(String id, String reason) {
        update(id, 2, reason, null, null);
    }

}
